package taskhandler

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"sync"

	"cvevulner/common"
	"cvevulner/models"
	"cvevulner/util"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
)

var wgx sync.WaitGroup

type HookData struct {
	Id         int64
	HookUrl    string
	CreateTime string
	Password   string
	ResultCode int
	ProjectId  int64
}

func ProcHookEvent(hookurl, owner, accessToken, pwd, gaussOwner, gitGaussToken string) error {
	deleteHook, ok := beego.AppConfig.Int("hook::delete_hook")
	if ok != nil {
		deleteHook = 1
	}
	hookId := int64(0)
	count := 5
	for ; ; {
		iHook, ihOk := models.GetAllIssueHook(hookId, count, 1)
		if ihOk && len(iHook) > 0 {
			for _, ih := range iHook {
				var hd []HookData
				if ih.Owner == gaussOwner {
					accessToken = gitGaussToken
				}
				GetDepositHooks(accessToken, ih.Owner, ih.Repo, &hd)
				if len(hd) > 0 {
					PrcMutDepositHooks(accessToken, pwd, ih, hd, deleteHook, gaussOwner)
				}
			}
			hookId = iHook[len(iHook)-1].Id
		} else {
			break
		}
	}
	// Check whether the warehouse is missing
	dbRepo := models.QueryOrgAllRepo(owner)
	if len(dbRepo) > 0 {
		for _, v := range dbRepo {
			var hd []HookData
			GetDepositHooks(accessToken, owner, v.Path, &hd)
			if len(hd) > 0 {
				for _, hdx := range hd {
					if hdx.HookUrl == hookurl && pwd == hdx.Password {
						var localIh models.IssueHooks
						localIh.HookUrl = hookurl
						localIh.Repo = v.Path
						localIh.HookId = hdx.Id
						localIh.Owner = owner
						PrcMutDepositHooks(accessToken, pwd, localIh, hd, deleteHook, gaussOwner)
					}
				}
			}
		}
	}
	return nil
}

func GetDepositHooks(accessToken, owner, repo string, hd *[]HookData) {
	url := fmt.Sprintf("https://gitee.com/api/v5/repos/%v/%v/hooks?access_token=%v&page=1&per_page=50", owner, repo, accessToken)
	hookData, err := util.HTTPGet(url)
	if err == nil && hookData != nil {
		for _, value := range hookData {
			if _, ok := value["id"]; !ok {
				logs.Error("hookData, err: ", ok, "url: ", url)
				continue
			}
			var hdl HookData
			hdl.Id = int64(value["id"].(float64))
			hdl.HookUrl = value["url"].(string)
			hdl.CreateTime = value["created_at"].(string)
			hdl.Password = value["password"].(string)
			hdl.ProjectId = int64(value["project_id"].(float64))
			hdl.ResultCode = int(value["result_code"].(float64))
			*hd = append(*hd, hdl)
		}
	}
}

func PrcMutDepositHooks(accessToken, pwd string, ihk models.IssueHooks,
	hd []HookData, deleteHook int, gaussOwner string) {
	for _, hdk := range hd {
		if ihk.HookUrl == hdk.HookUrl && hdk.Password == pwd {
			if deleteHook == 2 {
				go DeleteRepoHooks(accessToken, ihk.Owner, ihk.Repo, hdk.Id, ihk.Id)
			} else {
				if hdk.Id != ihk.HookId && ihk.HookId > 0 {
					go DeleteDepositHooks(accessToken, ihk.Owner, ihk.Repo, hdk.Id)
				}
			}
		}
	}
}

func DeleteRepoHooks(accessToken, owner, repo string, hookId, ihkId int64) {
	DeleteDepositHooks(accessToken, owner, repo, hookId)
	models.UpdateIssueHook(ihkId, 2)
}

func DeleteDepositHooks(accessToken, owner, repo string, hookId int64) {
	url := fmt.Sprintf("https://gitee.com/api/v5/repos/%v/%v/hooks/%v", owner, repo, hookId)
	requestBody := fmt.Sprintf(`{
					"access_token": "%s"
					}`, accessToken)
	util.HTTPDelCom(url, requestBody)
}

func AddRepoWebhook(accessToken, owner, path, pwd, hookurl string) error {
	var ih models.IssueHooks
	ih.CveId = int64(1)
	issueId := int64(1)
	ih.IssueNum = "000000"
	ih.Owner = owner
	ih.Status = 1
	ih.Repo = path
	ihs, errh := models.GetIssueHook(&ih)
	if errh {
		hookExist := false
		for _, wh := range ihs {
			if wh.HookUrl == hookurl {
				hookExist = true
				break
			}
		}
		if !hookExist {
			postErr := PostWebHooks(accessToken, owner, path, hookurl, pwd, ih.IssueNum, issueId, ih.CveId)
			if postErr == nil {
				logs.Info("owner: ", owner, ",repo: ", path, ",webhook created successfully")
			} else {
				logs.Error("owner: ", owner, ",repo: ", path, ", postErr: ", postErr)
			}
			return postErr
		}
		return nil
	} else {
		postErr := PostWebHooks(accessToken, owner, path, hookurl, pwd, ih.IssueNum, issueId, ih.CveId)
		if postErr == nil {
			logs.Info("owner: ", owner, ",repo: ", path, ",webhook created successfully")
		} else {
			logs.Error("owner: ", owner, ",repo: ", path, ", postErr: ", postErr)
		}
		return postErr
	}
}

//GetOrgRepos get organization repository
func GetOrgAllRepos(accToken, org, pwd, hookurl string, page int64) {
	wgx.Add(1)
	defer wgx.Done()
	resp, err := http.Get(fmt.Sprintf(GiteOrgReposURL, org, accToken, page, perPage))
	if err != nil {
		logs.Error("Get, GiteOrgReposURL: ", GiteOrgReposURL, ", org: ", GiteOrgReposURL, ",err: ", err)
		return
	}
	defer resp.Body.Close()
	var reps []models.HookRepository
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logs.Error("ReadAll, GiteOrgReposURL: ", GiteOrgReposURL, ", org: ", GiteOrgReposURL, ",err: ", err)
		return
	}
	err = json.Unmarshal(body, &reps)
	if err != nil {
		logs.Error("Unmarshal, GiteOrgReposURL: ", GiteOrgReposURL, ", org: ", GiteOrgReposURL, ",err: ", err)
		return
	}
	for _, v := range reps {
		AddRepoWebhook(accToken, org, v.Name, pwd, hookurl)
	}
}

func GetAllRepoInfo(accessToken, owner, pwd, hookurl string) error {
	orgInfo, err := GetOrgInfo(accessToken, owner)
	if err != nil {
		logs.Error("GetOrgInfo, org: ", owner, ",err: ", err)
		return err
	}
	reposNum := orgInfo.PublicRepos + orgInfo.PrivateRepos
	if reposNum <= 0 {
		logs.Info(fmt.Sprintf("%v contain %v repository,grab issue finish!", owner, reposNum))
		return errors.New(fmt.Sprintf("%v contain %v repository,grab issue finish!", owner, reposNum))
	}
	pageSize := reposNum / int64(perPage)
	if reposNum%int64(perPage) > 0 {
		pageSize = pageSize + 1
	}
	var i int64
	for i = 1; i <= pageSize; i++ {
		go GetOrgAllRepos(accessToken, owner, pwd, hookurl, i)
	}
	wgx.Wait()
	return nil
}

// Create webhook event function entry
func CreateHookEvent(hookurl, owner, accessToken, pwd, gaussOwner, gitGaussToken string) error {
	repoErr := GetAllRepoInfo(accessToken, owner, pwd, hookurl)
	if repoErr == nil {
		logs.Info("CreateHookEvent, owner: ", owner, ", webhook created successfully, hookurl: ", hookurl)
	} else {
		logs.Error("CreateHookEvent, owner: ", owner, ", hookurl: ", hookurl, ",repoErr: ", repoErr)
	}
	dbRepo := models.QueryOrgAllRepo(owner)
	if len(dbRepo) > 0 {
		for _, v := range dbRepo {
			AddRepoWebhook(accessToken, owner, v.Path, pwd, hookurl)
		}
	}
	return nil
}

func CheckAffectVerComplete(affectedVersion, packageName, version string, organizateId int8) []string {
	affectBranchsxList := make([]string, 0)
	affectProductList := make([]string, 0)
	unFixValue := make([]string, 0)
	owner, accessToken := common.GetOwnerAndToken("", organizateId)
	if organizateId == 1 || organizateId == 2 {
		affectBranchsxList, _ = GetBranchesInfo(accessToken, owner, packageName, organizateId)
	} else if organizateId == 3 || organizateId == 4 {
		affectBranchsxList = CreateBrandAndTags(accessToken, owner, packageName, organizateId)
	}
	if len(affectBranchsxList) == 0 {
		return unFixValue
	}
	if affectedVersion != "" && len(affectedVersion) > 1 {
		affectProductList = strings.Split(affectedVersion, ",")
	}
	if len(affectBranchsxList) > 0 {
		if len(affectProductList) == 0 {
			unFixValue = append(unFixValue, affectBranchsxList...)
		} else {
			for _, abl := range affectBranchsxList {
				flag := false
				for _, apl := range affectProductList {
					branchSlice := []string{}
					if strings.Contains(apl, ":") {
						branchSlice = strings.Split(apl, ":")
					} else if strings.Contains(apl, "：") {
						branchSlice = strings.Split(apl, "：")
					} else {
						branchSlice = append(branchSlice, apl)
					}
					if len(branchSlice) == 2 {
						affBrand := common.BranchVersionRep(branchSlice[0])
						if strings.TrimSpace(affBrand) == abl {
							if len(strings.TrimSpace(branchSlice[1])) > 1 {
								if (strings.TrimSpace(branchSlice[1]) == "受影响") ||
									(strings.TrimSpace(branchSlice[1]) == "不受影响") {
									flag = true
								}
							}
						}
					}
				}
				if !flag {
					unFixValue = append(unFixValue, abl)
				}
			}
		}
	}
	logs.Info("The branches that require mandatory verification are: ", unFixValue)
	return unFixValue
}

func QueryEulerRepoOrigin(affectedBranchs, packageName, version string, organizationID int8) string {
	affectBranchsxList := make([]string, 0)
	unFixValue := make([]string, 0)
	if affectedBranchs != "" && len(affectedBranchs) > 0 {
		affectBranchsxList = strings.Split(affectedBranchs, ",")
	}
	if len(affectBranchsxList) == 0 {
		return affectedBranchs
	}
	eulerOrgList := models.QueryEulerRepoOriginByNmae(packageName)
	if len(eulerOrgList) > 0 {
		for _, eol := range eulerOrgList {
			if len(eol.Branchs) > 0 {
				for _, abl := range affectBranchsxList {
					branSlice := strings.Split(eol.Branchs, ",")
					for _, bs := range branSlice {
						if abl == bs {
							unFixValue = append(unFixValue, abl)
							break
						}
					}
				}
			}
		}
	}
	if len(unFixValue) == 0 {
		accessToken := os.Getenv("GITEE_TOKEN")
		owner := beego.AppConfig.String("gitee::owner")
		branchList, errBrands := GetBranchesInfo(accessToken, owner, packageName, organizationID)
		if branchList == nil || len(branchList) == 0 {
			logs.Error("ProcIssue, Failed to obtain branch information,CveNum: ",
				", path: ", packageName, ", err: ", errBrands)
			return ""
		}
		unFixValue = append(unFixValue, branchList...)
	}
	if len(unFixValue) > 0 {
		valueSlice := removeDuplicateElement(unFixValue)
		return strings.Join(valueSlice, ",")
	}
	return ""
}

func removeDuplicateElement(element []string) []string {
	result := make([]string, 0, len(element))
	temp := map[string]struct{}{}
	for _, item := range element {
		if _, ok := temp[item]; !ok {
			temp[item] = struct{}{}
			result = append(result, item)
		}
	}
	return result
}
